#include "gtlStructures.h"
#include "gtlStrings.h"
#include "gtlDebug.h"
#include "gtlGeometry2D.h"

#ifdef GTL_DEBUG_STRUCTURES
#define find_as(a,b) find(a)
#endif

using GTL::Strings::StringCompareFunctor;

namespace GTL
{
  namespace Structures
  {
		/*
    struct StringComparer
    {
      bool operator() (const eastl::string& str, const char* cstr) { return str < cstr; }
      bool operator() (const char* cstr, const eastl::string& str) { return str > cstr; }
    };
		*/
    bool DataNode::HasElement(const char* key) const
    {
      if (Type != DICT) return 0; //-V104

      DictType::const_iterator i = Dict.find_as(key, StringCompareFunctor());
      return i != Dict.end();
    }


    bool DataNode::ElementHasStringValue(const char* key, const char* val) const
    {
      if (Type != DICT) return false; //-V104

      DictType::const_iterator i = Dict.find_as(key, StringCompareFunctor());
      if (i != Dict.end())
        return i->second->Type == STRING && i->second->String == val;
      return false;
    }

    const DataNode* DataNode::GetElement(const char* key) const
    {
      if (Type != DICT) return 0; //-V104

      DictType::const_iterator i = Dict.find_as(key, StringCompareFunctor());
      if (i != Dict.end())
        return i->second;
      return 0;
    }

    const DataNode* DataNode::GetElementSafe(const char* key) const
    {
			static DataNode empty;
      if (Type != DICT) return &empty; //-V104

      DictType::const_iterator i = Dict.find_as(key, StringCompareFunctor());
      if (i != Dict.end())
        return i->second;
      return this;
    }

    double DataNode::GetElementAsNumber(const char* key, double default) const
    {
      if (Type != DICT) return default;

      DictType::const_iterator i = Dict.find_as(key, StringCompareFunctor());
      if (i != Dict.end())
      {
        if (i->second && i->second->Type == NUMBER)
          return i->second->Number;
      }
      return default;
    }

    eastl::string DataNode::GetElementAsString(const char* key, const char* default) const
    {
      if (Type != DICT) return default;

      DictType::const_iterator i = Dict.find_as(key, StringCompareFunctor());
      if (i != Dict.end())
      {
        if (i->second && i->second->Type == STRING)
          return i->second->String;
      }
      return default;
    }

    void DataNode::Destroy()
    {
      for (ListType::iterator i=List.begin(); i!=List.end(); ++i)
      {
        delete *i;
      }

      for (DictType::iterator i=Dict.begin(); i!=Dict.end(); ++i)
      {
        delete i->second;
      }

      Dict.clear();
      String.clear();
      List.clear();
      Number = 0;
      Type = NONE;
    }

    eastl::string DataNode::ValueAsString(const char* default) const
    {
      if (Type == STRING)
        return String;
      if (Type == NUMBER)
      {
        char temp[256];
        sprintf_s(temp, "%f", Number);
        return temp;
      }
      return default;
    }

    double DataNode::ValueAsNumber(double default) const
    {
      if (Type == STRING)
      {
        return atof(String.c_str());
      }
      if (Type == NUMBER)
        return Number;
      return default;
    }

		bool DataNode::IsString( const char* str ) const
		{
			return Type == STRING && String == str;
		}

		DataNode& DataNode::operator[]( const char* str )
		{
			if (Type != DICT)
			{
				for (ListType::iterator i=List.begin(); i!=List.end(); ++i)
				{
					delete *i;
				}
				String.clear();
			}

			Type = DICT;

			DictType::iterator it = Dict.find_as(str, StringCompareFunctor());
			if (it != Dict.end())
			{
				return *(it->second);
			} else
			{
				DataNode* n = Create();
				Dict[str] = n;
				return *n;
			}
		}

		DataNode& DataNode::operator=( const DataNode& n )
		{
			Destroy();
			Type = n.Type;
			switch (Type)
			{
			case STRING:
				String = n.String;
				break;
			case NUMBER:
				Number = n.Number;
				break;
			case LIST:
				List = n.List;
				break;
			case DICT:
				Dict = n.Dict;
				break;
			}
			return *this;
		}
    /// DataNodeGEncode

    const char* DataNodeGEncode::Parse(const char* str)
    {
      char t = *str;
      switch (t)
      {
      case 'i':
        Type = NUMBER;
        return ParseNumber(str+1);
      case 's':
        Type = STRING;
        return ParseString(str+1);
      case '[':
        Type = LIST;
        return ParseList(str+1);
      case '{':
        Type = DICT;
        return ParseDict(str+1);
      case '#':
        Type = NONE;
        return str+1;
      }
      return str;
    }

    const char* DataNodeGEncode::ParseDict(const char* str)
    {
      while (*str != '}')
      {
        GTL_ASSERT(*str == 's');
        char* finish = 0;
        long int len = strtol(str+1, &finish, 10);
        GTL_ASSERT(*finish == '\'');
        str = ++finish;
        finish += len;
        GTL_ASSERT(*finish == '\'');
        eastl::string key(str, (const char*)finish);
        finish++;
        GTL_ASSERT(*finish == ':');
        str = finish+1;


        DataNodeGEncode* value = new DataNodeGEncode;
        str = value->Parse(str);
        Dict[key] = value;
      }
      return str+1;
    }

    const char* DataNodeGEncode::ParseNumber(const char* str)
    {
      char* finish = 0;
      Number = strtod(str, &finish);
      GTL_ASSERT(*finish == ';');
      return finish+1;
    }

    const char* DataNodeGEncode::ParseString(const char* str)
    {
      char* finish = 0;
      long int len = strtol(str, &finish, 10);
      GTL_ASSERT(*finish == '\'');
      str = ++finish;
      finish += len;
      GTL_ASSERT(*finish == '\'');
      String.assign(str, (const char*)finish);
      return finish+1;
    }

    const char* DataNodeGEncode::ParseList(const char* str)
    {
      while (*str != ']')
      {
        DataNodeGEncode* gen = new DataNodeGEncode;
        str = gen->Parse(str);
        List.push_back(gen);
      }
      return str+1;
    }

    /// DataNodeWilson

    const char* DataNodeWilson::Parse( const char* str )
    {
      str = StripWhitespace(str);
      for (;;)
      {
        char t = *str;
        switch (t)
        {
        case '"':
        case '\'':
          Type = STRING;
          return ParseString(str);
        case '[':
          Type = LIST;
          return ParseList(str+1);
        case '{':
          this->Type = DICT;
          return ParseDict(str+1);
        case '#':
          Type = NONE;
          return str+1;
        case 0:
          Type = NONE;
          return str;
				case ';':
				case ',':
					str++;
					continue;
        default:
          if (isalpha(t))
            return ParseWordString(str);
          else if (isdigit(t) || t == '-')
            return ParseNumber(str);
					else
					{
						Type = NONE;
						return 0;
					}
        }
      }
    }

    const char* DataNodeWilson::StripWhitespace( const char* str )
    {
      while (*str && isspace(*str))
        str++;
      return str;
    }

    const char* DataNodeWilson::ParseNumber( const char* str )
    {
      Type = NUMBER;
      char* finish = 0;
      Number = strtod(str, &finish);
      return finish;
    }

    const char* DataNodeWilson::ParseString( const char* str )
    {
      const char* first = str;
      str++;
      char term[2] = " ";
      *term = *first;
			
			const char* cur = str;
			size_t terminator_at = 0;
			do 
			{
				terminator_at = strcspn(cur, term);
				cur = cur+terminator_at+1;
			} while (*(cur-2) == '\\');

      GTL_ASSERT(*(str+terminator_at) == *first);
      String.assign(str, str+terminator_at);
      return str+terminator_at+1;
    }

    const char* DataNodeWilson::ParseWordString( const char* str )
    {
      const char* first = str;
      str++;
      while (*str && isalnum(*str))
        str++;
      if (!strncmp(first, "true", str-first))
      {
        Type = NUMBER;
        Number = 1.0;
      } else if (!strncmp(first, "false", str-first))
      {
        Type = NUMBER;
        Number = 0.0;
      } else if (!strncmp(first, "null", str-first) && !strncmp(first, "none", str-first))
      {
        Type = NONE;
      } else
      {
        Type = STRING;
        String.assign(first, str);
      }
      return str;
    }

    const char* DataNodeWilson::ParseList( const char* str )
    {
      str = StripWhitespace(str);
      while (*str && *str != ']')
      {
        str = StripWhitespace(str);
        DataNodeWilson* node = new DataNodeWilson;
        str = node->Parse(str);
        List.push_back(node);

        str = StripWhitespace(str);
        if (*str == ',' || *str == ';')
          str = StripWhitespace(str+1);
      }

      if (*str)
        return str+1;

      return str;
    }

    const char* DataNodeWilson::ParseDict( const char* str )
    {
      str = StripWhitespace(str);
      while (*str && *str != '}')
      {
        str = StripWhitespace(str);
        const char* first = str;
        //str++;
        eastl::string key;
        if (*first == '"' || *first == '\'')
        {
					str++;

          /// quoted string
          while (*str && *str != *first)
            str++;
          if (!*str)
            return str;

          key.assign(first+1, str);

          str++; /// skip terminator
        } else if (isalpha(*first))
        {
          /// single-word string
          while (*str && isalnum(*str))
            str++;
          if (!*str)
            return str;

          key.assign(first, str);
        } else
        {
					GTL_CRITICAL_FAILURE("While reading Wilson dictionary: Dictionary key not a string (starts with %c)", *first);
          return str;
        }

        str = StripWhitespace(str);
        if (*str == ':' || *str == '=')
          str++;

        DataNodeWilson* node = new DataNodeWilson;
        str = node->Parse(str);
        Dict[key] = node;

        str = StripWhitespace(str);
        if (*str == ',' || *str == ';')
          str = StripWhitespace(str+1);
      }

      if (*str)
        return str+1;
      return str;
    }

		eastl::string DataNodeWilson::Encode() const
		{
			switch (Type)
			{
			case NONE:
				return "#";
			case NUMBER:
				{
					char buf[32];
					sprintf(buf, "%f", this->Number);
					return buf;
				}
			case STRING:
				return eastl::string("\"") + GTL::Strings::AddSlashes(this->String) + "\"";
			case LIST:
				{
					eastl::string str;
					str = "[ ";
					for (ListType::const_iterator i=List.begin(); i!=List.end(); i++)
					{
						DataNodeWilson* node = (DataNodeWilson*)(*i);
						str += node->Encode();
						str += ", \n";
					}
					str += "]\n";
					return str;
				}
			case DICT:
				{
					eastl::string str;
					str = "{\n";
					for (DictType::const_iterator i=Dict.begin(); i!=Dict.end(); i++)
					{
						DataNodeWilson* node = (DataNodeWilson*)(i->second);
						str += eastl::string("\"") + GTL::Strings::AddSlashes(i->first) + "\" = ";
						str += node->Encode();
						str += ", \n";
					}
					str += "}\n";
					return str;
				}
			}

			return "";
		}

	} /// end namespace Structures

	template <>
	bool NodeToObject<float>(float& obj, const GTL::Structures::DataNode* node)
	{
		if (node->Type != GTL::Structures::DataNode::NUMBER)
			return false;

		obj = (float)node->Number;
		return true;
	}

	template <>
	bool NodeToObject<double>(double& obj, const GTL::Structures::DataNode* node)
	{
		if (node->Type != GTL::Structures::DataNode::NUMBER)
			return false;

		obj = node->Number;
		return true;
	}


	typedef GTL::Geometry2D::GenericPoint2D<float> PointF;
	template <>
	bool NodeToObject<PointF>(PointF& obj, const GTL::Structures::DataNode* node)
	{
		if (node->Type == GTL::Structures::DataNode::LIST)
		{
			if (node->List.size() >= 2)
			{
				obj.X = (float)(*node->List.begin())->ValueAsNumber();
				obj.Y = (float)(*(++node->List.begin()))->ValueAsNumber();
			} else return false;
		} else if(node->Type == GTL::Structures::DataNode::DICT)
		{
			obj.X = (float)node->GetElementAsNumber("x", 0.0);
			obj.X = (float)node->GetElementAsNumber("y", 0.0);
		} else if (node->Type == GTL::Structures::DataNode::NUMBER)
		{
			obj.X = obj.Y = (float)node->Number;
		}

		return false;
	}
}